using System;
using System.Collections.Generic;
using CI.Interfaces;
using System.Runtime.Serialization;

namespace CI.Organism
{
    /// <summary>The organisms DNA</summary>
    [DataContract(IsReference = true)]
    public class DNA : IDNA
    {
        #region Properties

        /// <summary>
        /// The list of chromosones in the DNA
        /// </summary>    
        [DataMember]
        public IChromosomeCollection Chromosomes { get; set; }

        /// <summary>
        /// The list of actioning Genes
        /// </summary>
        public IGeneCollection ActioningGenes { get; set; }

        #endregion

        protected static SimEnv SimEnv { get { return SimEnv.Instance; } }

        /// <summary>
        /// Causes the DNA to be processed resulting in a growing state of the organisms, usually triggered externaly in a hosting environment by the organsims heartbeat
        /// </summary>
        public virtual bool Grow()
        {
            // make sure we have chromosomes to work with
            if (Chromosomes == null)
                throw new DNAException("Chromosome Collection is Empty(null)");


            SimEnv.TraceLogger.LogDebug(string.Format("Growing Organisms Chromosomes count : {0}", Chromosomes.Count));
            // create the actioning list
            ActioningGenes = new GeneCollection();

            // loop through each chromosome
            foreach (Chromosome chromosome in Chromosomes)
            {
                // make sure we have genes to work with
                if (chromosome.Genes == null)
                    throw new DNAException("Gene Collection is Empty(null)");

                SimEnv.TraceLogger.LogDebug(string.Format("Organism Chromosome index : {1}  Gene count : {0}", chromosome.Genes.Count, Chromosomes.IndexOf(chromosome)));

                // loop through the each gene
                foreach (IGene gene in chromosome.Genes)
                {
                    // if the gene does not meets the requirements in list skip to the next one
                    if (!gene.Actionable())
                        continue;

                    // Execute the genes cell action
                    gene.Action();

                    // mark the gene as actioning
                    ActioningGenes.Add(gene);
                }
            }

            // now loop through for all actioning genes and mark them as actioned
            foreach (Gene gene in ActioningGenes)
            {
                gene.Actioned = true;
            }

            return true;
        }

        /// <summary>
        /// Take this parents DNA and create a new DNA to be put in the new child Organism
        /// Mutate the current DNA and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childOrganism">child Organism it will be going into</param>
        /// <returns>new dna for child</returns>
        public IDNA Mutate(double mutationRate, IOrganism childOrganism)
        {
            // create the child dna
            return new DNA
                               {
                                   // create the chromosomes based on this instances chromosomes
                                   Chromosomes = Chromosomes.Mutate(mutationRate, childOrganism)
                               };
        }

        /// <summary>
        /// do a equals comparison of one dna type to another only comparing the value types, no refference comparisons
        /// </summary>
        /// <param name="dna">dna to compare against</param>
        /// <returns>true if equal value types</returns>
        public bool EqualsValueTypes(IDNA dna)
        {
            // check the basics
            if (dna == null)
                return false;

            if (Chromosomes.Count != dna.Chromosomes.Count)
                return false;

            // check each chromosome
            foreach (var chromosome in Chromosomes)
            {
                // find a chromsome
                var foundChromosome = dna.Chromosomes.Find(chromosome.Guid);

                // if none found then exit
                if (foundChromosome == null)
                    return false;

                if (!chromosome.EqualsValueTypes(foundChromosome))
                    return false;
            }

            return true;
        }


        /// <summary>
        // updates all the cells in the dna to the cells in the collection as they will have changed after mutations
        /// </summary>
        /// <param name="cells"></param>
        public void UpdateMutatedCells(ICellCollection cells)
        {
            foreach (var chromosome in Chromosomes)
            {
                foreach (var gene in chromosome.Genes)
                {
                    if (gene is GeneCellular)
                    {
                        var geneCellular = (GeneCellular)gene;
                        geneCellular.Cell = cells.Find(geneCellular.Cell.Guid);

                        if (geneCellular.Cell is INeuron)
                        {
                            var neuron = (INeuron)geneCellular.Cell;

                            UpdateSynapses(cells, neuron.InputSynapse);
                            UpdateSynapses(cells, neuron.OutputSynapse);
                        }
                    }

                    foreach (var requirement in gene.Requirements)
                    {
                        if (requirement is HBOffsetTimeCell)
                        {
                            var hbOffsetTimeCell = (HBOffsetTimeCell)requirement;
                            hbOffsetTimeCell.Cell = cells.Find(hbOffsetTimeCell.Cell.Guid);
                        }
                    }
                }
            }
        }

        private static void UpdateSynapses(ICellCollection cells, IEnumerable<ISynapse> synapseCollection)
        {
            var organism = cells.FindPhysicalZygote().Organism;

            foreach (var synapse in synapseCollection)
            {
                if (synapse.TargetNotification != null)
                    synapse.AddSynapticConnectionTarget(GetSynapticConneciton(synapse.TargetNotification.Guid, cells, organism));

                if (synapse.SourceNotification != null)
                    synapse.AddSynapticConnectionSource(GetSynapticConneciton(synapse.SourceNotification.Guid, cells, organism));
            }
        }

        private static ISynapticConnection GetSynapticConneciton(Guid guid, ICellCollection cells, IOrganism organism)
        {
            return (INeuron)cells.Find(guid) ?? (ISynapticConnection)organism.GetSynapseIO(guid);
        }
    }
}
